Bank marketing is a crucial strategy for any financial institution that wants to stand out from the competition and build a loyal customer base. Bank marketing involves creating a unique brand image, offering innovative products and services, and delivering quality and value to the customers.
One of the products that banks can use to attract and retain customers is term deposits. Term deposits are deposit accounts that have a fixed maturity date and a fixed interest rate. Term deposits are appealing to customers who want to save money for a specific goal or who want to earn a higher interest rate than a regular savings account.
Term deposits also benefit the banks, as they provide a stable source of funding and reduce the risk of liquidity problems. Term deposits can help banks to diversify their portfolio and manage their interest rate risk. Term deposits can also enhance the bank's reputation and customer relationship, as they show that the bank values the customer's trust and loyalty.
However, term deposits also pose some challenges for both the customers and the banks. Customers may face penalties or fees if they withdraw their money before the maturity date. Customers may also lose out on better interest rates or investment opportunities if the market conditions change. Banks may face the risk of losing customers if they fail to offer competitive interest rates or renewals. Banks may also have to deal with mismatched assets and liabilities if the term deposits have different maturities than the loans they fund.
Therefore, bank marketing and customer relationship management are essential for ensuring the success of term deposits. Bank marketing should focus on highlighting the benefits of term deposits, such as security, stability, and guaranteed returns. Bank marketing should also target the right customers, such as those who have a low-risk appetite, a long-term saving horizon, or a specific financial goal. Bank marketing should also use various channels, such as online platforms, social media, or direct mail, to reach out to potential and existing customers.
Customer relationship management should focus on providing excellent service and support to the term deposit holders. Customer relationship management should also involve offering personalized advice and solutions, such as flexible terms, attractive rates, or value-added features. Customer relationship management should also aim to retain the customers after the term deposit matures, by offering them other products or services that suit their needs and preferences.
Term deposits are a valuable tool for bank marketing and customer relationship management. By using term deposits effectively, banks can increase their customer base, enhance their brand image, and improve their profitability.
The business goal of this project is to reduce the marketing resources by identifying the customers who would be interested in subscribing to a term deposit. By doing so, the marketing team can focus their efforts on the most promising leads and increase the conversion rate. This will also improve the customer satisfaction and loyalty, as they will receive more relevant and personalized offers.
| Attribute | Description |
|---|---|
| age | numeric |
| job | type of job (categorical: "admin.", "unknown", "unemployed", "management", "housemaid", "entrepreneur", "student", "blue-collar", "self-employed", "retired", "technician", "services") |
| marital | marital status (categorical: "married", "divorced", "single"; note: "divorced" means divorced or widowed) |
| education | categorical: "unknown", "secondary", "primary", "tertiary" |
| default | has credit in default? (binary: "yes", "no") |
| balance | average yearly balance, in euros (numeric) |
| housing | has housing loan? (binary: "yes", "no") |
| loan | has personal loan? (binary: "yes", "no") |
| contact | contact communication type (categorical: "unknown", "telephone", "cellular") |
| day | last contact day of the month (numeric) |
| month | last contact month of year (categorical: "jan", "feb", "mar", ..., "nov", "dec") |
| duration | last contact duration, in seconds (numeric) |
| campaign | number of contacts performed during this campaign and for this client (numeric, includes last contact) |
| pdays | number of days that passed by after the client was last contacted from a previous campaign (numeric, -1 means client was not previously contacted) |
| previous | number of contacts performed before this campaign and for this client (numeric) |
| poutcome | outcome of the previous marketing campaign (categorical: "unknown", "other", "failure", "success") |
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as px
import seaborn as sns
import datetime as dt
import plotly.graph_objects as go
import warnings
warnings.filterwarnings('ignore')
df = pd.read_csv('bank.csv')
df.head()
| age | job | marital | education | default | balance | housing | loan | contact | day | month | duration | campaign | pdays | previous | poutcome | deposit | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 59 | admin. | married | secondary | no | 2343 | yes | no | unknown | 5 | may | 1042 | 1 | -1 | 0 | unknown | yes |
| 1 | 56 | admin. | married | secondary | no | 45 | no | no | unknown | 5 | may | 1467 | 1 | -1 | 0 | unknown | yes |
| 2 | 41 | technician | married | secondary | no | 1270 | yes | no | unknown | 5 | may | 1389 | 1 | -1 | 0 | unknown | yes |
| 3 | 55 | services | married | secondary | no | 2476 | yes | no | unknown | 5 | may | 579 | 1 | -1 | 0 | unknown | yes |
| 4 | 54 | admin. | married | tertiary | no | 184 | no | no | unknown | 5 | may | 673 | 2 | -1 | 0 | unknown | yes |
df.columns
Index(['age', 'job', 'marital', 'education', 'default', 'balance', 'housing',
'loan', 'contact', 'day', 'month', 'duration', 'campaign', 'pdays',
'previous', 'poutcome', 'deposit'],
dtype='object')
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 11162 entries, 0 to 11161 Data columns (total 17 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 age 11162 non-null int64 1 job 11162 non-null object 2 marital 11162 non-null object 3 education 11162 non-null object 4 default 11162 non-null object 5 balance 11162 non-null int64 6 housing 11162 non-null object 7 loan 11162 non-null object 8 contact 11162 non-null object 9 day 11162 non-null int64 10 month 11162 non-null object 11 duration 11162 non-null int64 12 campaign 11162 non-null int64 13 pdays 11162 non-null int64 14 previous 11162 non-null int64 15 poutcome 11162 non-null object 16 deposit 11162 non-null object dtypes: int64(7), object(10) memory usage: 1.4+ MB
sns.histplot(x=df['age'], label='Age', color='skyblue', kde=True)
plt.axvline(x=df['age'].mean(), color='red', linestyle="--", label='Mean Age: {:.2f}'.format(df['age'].mean()))
plt.legend()
plt.title('Distribution of Age')
plt.show()
sns.kdeplot(x=df['duration'], color='skyblue', shade=True, label='Duration')
plt.axvline(x=df['duration'].mean(), color='red', linestyle="--", label='Mean Duration: {:.2f}'.format(df['duration'].mean()))
plt.legend()
plt.title('Distribution of Duration')
plt.show()
sns.kdeplot(x=df['pdays'], color='skyblue', shade=True, label='Duration')
plt.axvline(x=df['pdays'].mean(), color='red', linestyle="--", label='Mean Duration: {:.2f}'.format(df['duration'].mean()))
plt.legend()
plt.title('Distribution of pdays')
plt.show()
fig = px.bar(
df.job.value_counts().reset_index(),
x='job',
y='count',
color='job',
text='count',
)
fig.update_layout(
title_text='Job Type of the Customers'
)
fig = px.pie(
df.marital.value_counts().reset_index(),
names='marital',
values='count'
)
fig.update_layout(
showlegend=True,
legend_title_text='Marital Status'
)
fig.update_traces(textposition='outside', textinfo='percent+label')
fig.update_layout(
legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)
)
fig.show()
fig = px.bar(
df.education.value_counts().reset_index().rename(columns={'education': 'Education', 'count': 'Count'}),
x='Education',
y='Count',
color='Education',
text='Count',
)
fig.update_layout(
legend_title_text='Education',
title_text='Education of the Customers'
)
fig.show()
fig = px.pie(
df.education.value_counts().reset_index().rename(columns={'education': 'Education', 'count': 'Count'}),
names='Education',
values='Count',
)
fig.update_layout(
showlegend=True,
legend_title_text='Education'
)
fig.update_traces(textposition='outside', textinfo='percent+label')
fig.update_layout(
legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)
)
fig.show()
from plotly.subplots import make_subplots
fig = make_subplots(
rows=1,
cols=3,
specs=[[{"type": "pie"}, {"type": "pie"}, {"type": "pie"}]],
subplot_titles=('Credit in Default', 'Housing Loan', 'Personal Loan')
)
default_counts = df.default.value_counts().reset_index().rename(columns={'default': 'Default', 'count': 'Count'})
fig.add_trace(
go.Pie(
values=default_counts['Count'],
labels=default_counts['Default'],
name='Has Credit in Default?',
showlegend=False
),
row=1,
col=1
)
housing_counts = df.housing.value_counts().reset_index().rename(columns={'housing': 'Housing', 'count': 'Count'})
fig.add_trace(
go.Pie(
values=housing_counts['Count'],
labels=housing_counts['Housing'],
name='Housing Loan',
showlegend=False
),
row=1,
col=2
)
loan_counts = df.loan.value_counts().reset_index().rename(columns={'loan': 'Loan', 'count': 'Count'})
fig.add_trace(
go.Pie(
values=loan_counts['Count'],
labels=loan_counts['Loan'],
name='Personal Loan',
showlegend=True
),
row=1,
col=3
)
fig.update_layout(
showlegend=True,
legend_title_text=" ",
title_text='Credit and Loan Analysis'
)
fig = px.pie(
df.contact.value_counts().reset_index().rename(columns={'contact': 'Contact', 'count': 'Count'}),
names='Contact',
values='Count'
)
fig.update_layout(
showlegend=True,
legend_title_text='Contact'
)
fig.update_traces(textposition='outside', textinfo='percent+label')
fig.update_layout(
legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)
)
fig.show()
fig = px.bar(
df.month.value_counts().reset_index().rename(columns={'month': 'Month', 'count': 'Count'}),
x='Month',
y='Count',
color='Month',
text='Count',
)
fig.update_layout(
title_text='Last Contact Month of the Year'
)
fig = px.bar(
df.poutcome.value_counts().reset_index().rename(columns={'poutcome': 'Poutcome', 'count': 'Count'}),
x='Poutcome',
y='Count',
color='Poutcome',
text='Count',
)
fig.update_layout(
title_text='Outcome of Previous Marketing Campaign',
)
fig.show()
fig = px.pie(
df.poutcome.value_counts().reset_index().rename(columns={'poutcome': 'Poutcome', 'count': 'Count'}),
names='Poutcome',
values='Count',
)
fig.update_layout(
showlegend=True,
legend_title_text='Poutcome'
)
fig.update_traces(textposition='outside', textinfo='percent+label')
fig.update_layout(
title_text='Outcome of Previous Marketing Campaign'
)
fig.update_layout(
legend=dict(orientation='v', yanchor='middle', y=1.02, xanchor='right', x=1)
)
fig.show()
fig = px.pie(
df.groupby(['deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'}),
names='deposit',
values='Count'
)
fig.update_layout(
showlegend=True,
legend_title_text='Deposit'
)
fig.update_traces(textposition='outside', textinfo='percent+label')
fig.update_layout(
title_text='Term Deposits',
)
fig.update_layout(
legend=dict(orientation='v', yanchor='middle', y=1.02, xanchor='right', x=1)
)
fig.show()
a = df.groupby(['job'], as_index=False)['balance'].mean()
a['balance'] = round(a['balance'], 1)
fig = px.bar(
a.sort_values(by='balance', ascending=False),
x='job',
y='balance',
text='balance',
color='job',
)
fig.update_layout(
title_text='Average Balance of the Clients by Their Job Type',
legend_title_text='Job Type'
)
fig = px.box(
df,
x='job',
y='age',
color='job',
title='Distribution of Age Based on Job Type'
)
fig.update_layout(
legend_title_text="Job Type"
)
fig = px.box(
df,
x='marital',
y='age',
color='marital',
title='Distribution of Age Based on Marital Status'
)
fig.update_layout(
legend_title_text="Marital Status"
)
fig.show()
fig=px.box(
df,x='education',
y='age',
color='education',
title='Distribution of Age Based on Education Level')
fig.update_layout(
legend_title_text="Education Level")
fig.show()
fig=px.box(
df,x='housing',
y='age',
color='housing',
title='Distribution of age based on Housin Loan Status'
)
fig.update_layout(
legend_title_text="Housing Loan"
)
fig.show()
fig=px.box(
df,x='loan',
y='age',
color='loan',
title='Distribution of age based on Personal Loan Status'
)
fig.update_layout(
legend_title_text="Personal Loan"
)
fig.show()
fig=px.box(
df,x='deposit',
y='age',
color='deposit',
title='Distribution of age based on Term Deposit Status'
)
fig.update_layout(
legend_title_text="Term Deposit"
)
fig.show()
fig=px.box(
df,x='deposit',
y='pdays',
color='deposit',
title='Distribution of pdays based on Term Deposit Status'
)
fig.update_layout(
legend_title_text="Term Deposit"
)
fig.show()
fig=px.box(
df,x='poutcome',
y='pdays',
color='poutcome',
title='Effect of pdays on poutcome'
)
fig.update_layout(
legend_title_text="Credit in Default"
)
fig.update_layout(
legend_title_text="poutcome"
)
fig.show()
fig=px.box(
df,x='poutcome',
y='pdays',
color='deposit',
title='Effect of pdays on poutcome and term deposits'
)
fig.update_layout(
legend_title_text="Deposit"
)
fig.show()
fig=px.box(
df,x='deposit',
y='duration',
color='deposit'
,title='Effect of Duration on Term Deposit Status'
)
fig.update_layout(
legend_title_text="Term Deposit"
)
fig.show()
df['Duration'] = df['duration'].apply(lambda x: 'Above_Average_Duration' if x >= df['duration'].mean() else 'Below_Average_Duration')
a = df.groupby(['Duration', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'})
a['percent'] = round(a['Count'] * 100 / a.groupby('Duration')['Count'].transform('sum'), 1)
a['percent'] = a['percent'].apply(lambda x: '{}%'.format(x))
fig = px.bar(a, x='Duration', y='Count', text='percent', color='deposit', barmode='group')
fig.show()
df['Balance'] = df['balance'].apply(lambda x: 'Above_Average' if x >= df['balance'].mean() else 'Below_Average')
a = df.groupby(['Balance', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'})
a['percent'] = round(a['Count'] * 100 / a.groupby('Balance')['Count'].transform('sum'), 1)
a['percent'] = a['percent'].apply(lambda x: '{}%'.format(x))
fig = px.bar(a, x='Balance', y='Count', text='percent', color='deposit', barmode='group')
fig.update_layout(legend_title_text='Deposit Status:', title_text='Effect of Balance on Term Deposits')
fig.update_traces(textposition='outside')
fig.show()
k = df.groupby(['housing', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'})
fig = make_subplots(rows=1, cols=2, specs=[[{"type": "pie"}, {"type": "pie"}]])
fig.add_trace(go.Pie(values=k[k['housing'] == 'yes']['Count'], labels=k[k['housing'] == 'yes']['deposit'], hole=0.7, name='Having Housing Loan', showlegend=False), row=1, col=1)
fig.add_trace(go.Pie(values=k[k['housing'] == 'no']['Count'], labels=k[k['housing'] == 'no']['deposit'], hole=0.7, name='Not having Housing loan', showlegend=True), row=1, col=2)
fig.update_layout(title_x=0.5, showlegend=True, legend_title_text="Deposit", title_text='Term Deposits based on Housing Loan')
fig.add_annotation(dict(x=0.15, y=0.5, align='center', xref="paper", yref="paper", showarrow=False, font_size=22, text="Have a Loan"))
fig.add_annotation(dict(x=0.83, y=0.5, align='center', xref="paper", yref="paper", showarrow=False, font_size=22, text="No Loan"))
fig.update_traces(textposition='outside', textinfo='percent+label')
k = df.groupby(['loan', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'})
fig = make_subplots(rows=1, cols=2, specs=[[{"type": "pie"}, {"type": "pie"}]])
fig.add_trace(go.Pie(values=k[k['loan'] == 'yes']['Count'], labels=k[k['loan'] == 'yes']['deposit'], hole=0.7, name='Having Personal Loan', showlegend=False), row=1, col=1)
fig.add_trace(go.Pie(values=k[k['loan'] == 'no']['Count'], labels=k[k['loan'] == 'no']['deposit'], hole=0.7, name='Not having Personal loan', showlegend=True), row=1, col=2)
fig.update_layout(title_x=0.5, showlegend=True, legend_title_text="Deposit", title_text='Term Deposits based on Personal Loan')
fig.add_annotation(dict(x=0.15, y=0.5, align='center', xref="paper", yref="paper", showarrow=False, font_size=22, text="Have a Loan"))
fig.add_annotation(dict(x=0.83, y=0.5, align='center', xref="paper", yref="paper", showarrow=False, font_size=22, text="No Loan"))
fig.update_traces(textposition='outside', textinfo='percent+label')
d = df.groupby(['marital', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'})
fig = make_subplots(rows=1, cols=3, specs=[[{"type": "pie"}, {"type": "pie"}, {"type": "pie"}]], subplot_titles=('Divorced', 'Married', 'Single'))
fig.add_trace(go.Pie(values=d[d['marital'] == 'divorced']['Count'], labels=d[d['marital'] == 'divorced']['deposit'], name='Divorced', showlegend=False), row=1, col=1)
fig.add_trace(go.Pie(values=d[d['marital'] == 'married']['Count'], labels=d[d['marital'] == 'married']['deposit'], name='Married', showlegend=False), row=1, col=2)
fig.add_trace(go.Pie(values=d[d['marital'] == 'single']['Count'], labels=d[d['marital'] == 'single']['deposit'], name='Single', showlegend=True), row=1, col=3)
fig.update_layout(title_x=0.5, showlegend=True, legend_title_text="Deposit", title_text='Term Deposits based on Marital Status')
fig.update_traces(textposition='inside', textinfo='percent')
a = df.groupby(['job', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'})
a['percent'] = round(a['Count'] * 100 / a.groupby('job')['Count'].transform('sum'), 1)
a['percent'] = a['percent'].apply(lambda x: '{}%'.format(x))
fig = px.bar(a, x='job', y='Count', text='percent', color='deposit', barmode='group')
fig.update_layout(title_x=0.5, showlegend=True, title_text='Term Depositors based on Job Type')
fig.show()
fig = px.line(df.groupby(['age', 'loan'], as_index=False)['job'].count().rename(columns={'job': 'Count'}), x='age', y='Count', color='loan')
fig.update_layout(title_x=0.5, legend_title_text="Term Deposit", title_text='Effect of Age on Personal Loan')
fig.show()
fig = px.line(df.groupby(['age', 'housing'], as_index=False)['job'].count().rename(columns={'job': 'Count'}), x='age', y='Count', color='housing')
fig.update_layout(title_x=0.5, legend_title_text="Term Deposit", title_text='Effect of Age on Housing Loan')
fig.show()
fig = px.line(df.groupby(['age', 'deposit'], as_index=False)['job'].count().rename(columns={'job': 'Count'}), x='age', y='Count', color='deposit')
fig.update_layout(title_x=0.08, legend_title_text="Term Deposit", title_text='Effect of Age on Term Deposits')
fig.show()
a = df.groupby(['month', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'})
a['percent'] = round(a['Count'] * 100 / a.groupby('month')['Count'].transform('sum'), 1)
a['percent'] = a['percent'].apply(lambda x: '{}%'.format(x))
fig = px.bar(a, x='month', y='Count', text='percent', color='deposit', barmode='group')
fig.update_layout(title_x=0.5, showlegend=True, legend_title_text="Deposit", title_text='Deposits based on last Contact month')
fig.show()
fig = px.line(df.groupby(['campaign', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'}), x='campaign', y='Count', color='deposit')
fig.update_layout(title_x=0.08, legend_title_text="Term Deposit", title_text='Effect of Campaign on Term Deposits')
fig.show()
fig = px.line(df.groupby(['previous', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'}), x='previous', y='Count', color='deposit')
fig.update_layout(title_x=0.08, legend_title_text="Term Deposit", title_text='Effect of Previous Campaign on Term Deposits')
fig.show()
a = df.groupby(['poutcome', 'deposit'], as_index=False)['age'].count().rename(columns={'age': 'Count'})
a['percentile'] = round(a['Count'] * 100 / a.groupby('poutcome')['Count'].transform('sum'), 1)
a['percentile'] = a['percentile'].apply(lambda x: '{}%'.format(x))
fig = px.bar(a, x='poutcome', y='Count', color='deposit', text='percentile', barmode='group')
fig.update_layout(title_x=0.08, showlegend=True, legend_title_text="Deposit", title_text='Term Deposits based on Outcome of Previous Marketing Campaign')
fig.show()